package mgo

import (
	"context"
	"fmt"
	"github.com/pkg/errors"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"time"
)

type IndexInfo struct {
	Collection *mongo.Collection
	Ctx        context.Context
	Name       string                // 指定删除的索引名，DropOne 使用
	Models     []*IndexModel         // 指定字段索引信息
	Options    *IndexOperationOption // 设置操作方法的 Option 信息
	Result     interface{}           // 查询结果存储内存的地址指针
	// option
	CreateOptions *options.CreateIndexesOptions
	ListOptions   *options.ListIndexesOptions
	DropOptions   *options.DropIndexesOptions
	// 预处理数据
	models []mongo.IndexModel
}

// 索引操作的 Option 设置
type IndexOperationOption struct {
	BatchSize int32 // 设置查询索引列表时返回的最大文档数
	MaxTime   int64 // 设置操作最大执行时间，单位：秒
}

type IndexModel struct {
	Keys   []string
	Option *IndexOption
}

// IndexOption 单个索引的 Options 设置
type IndexOption struct {
	ExpireDefault      bool  // 设置指定文档根据指定字段设置的时间过期
	ExpireAfterSeconds int32 // 设置指定文档在设置后指定时间后过期，单位秒
	Unique             bool  // 设置唯一索引
	Hidden             bool  // 设置指定字段隐藏，不被查询到，MongoDB versions >= 4.4 才能使用
}

// initIndexInfo 初始化索引操作信息
func initIndexInfo(i *IndexInfo) error {
	if i == nil {
		return errors.New("mgo operation info cannot be nil")
	}
	if i.Collection == nil {
		return errors.New("mgo collection cannot be nil")
	}
	if i.Ctx == nil {
		i.Ctx = context.Background()
	}
	if len(i.Models) > 0 {
		i.models = make([]mongo.IndexModel, 0)
		for _, v := range i.Models {
			keys, option := buildIndexModel(v)
			i.models = append(i.models, mongo.IndexModel{Keys: keys, Options: option})
		}
	}
	return nil
}

func buildIndexModel(i *IndexModel) (bson.D, *options.IndexOptions) {
	// 解析 Keys
	var keys bson.D
	for _, v := range i.Keys {
		keys = append(keys, bson.E{v, 1})
	}
	// 解析 Option
	option := options.Index()
	if i.Option.ExpireDefault {
		option.SetExpireAfterSeconds(0)
	} else if i.Option.ExpireAfterSeconds == 0 {
		option.SetExpireAfterSeconds(i.Option.ExpireAfterSeconds)
	}
	if i.Option.Unique {
		option.SetUnique(true)
	}
	if i.Option.Hidden {
		option.SetHidden(true)
	}
	return keys, option
}

func CreateMany(info *IndexInfo) ([]string, error) {
	if err := initIndexInfo(info); err != nil {
		return nil, err
	}
	if len(info.models) == 0 {
		return nil, errors.New("mgo CreateMany：models cannot be empty")
	}
	if info.CreateOptions == nil {
		info.CreateOptions = options.CreateIndexes()
		if info.Options != nil {
			if info.Options.MaxTime != 0 {
				info.CreateOptions = info.CreateOptions.SetMaxTime(time.Duration(info.Options.MaxTime) * time.Second)
			}
		}
	}
	return info.Collection.Indexes().CreateMany(info.Ctx, info.models, info.CreateOptions)
}

func CreateOne(info *IndexInfo) (string, error) {
	if err := initIndexInfo(info); err != nil {
		return "", err
	}
	if len(info.models) == 0 {
		return "", errors.New("mgo CreateOne：models cannot be empty")
	}
	if info.CreateOptions == nil {
		info.CreateOptions = options.CreateIndexes()
		if info.Options != nil && info.Options.MaxTime != 0 {
			info.CreateOptions = info.CreateOptions.SetMaxTime(time.Duration(info.Options.MaxTime) * time.Second)
		}
	}
	fmt.Println(info.models[0])
	return info.Collection.Indexes().CreateOne(info.Ctx, info.models[0], info.CreateOptions)
}

func DropAll(info *IndexInfo) (string, error) {
	if err := initIndexInfo(info); err != nil {
		return "", err
	}
	if info.DropOptions == nil {
		info.DropOptions = options.DropIndexes()
		if info.Options != nil && info.Options.MaxTime != 0 {
			info.DropOptions = info.DropOptions.SetMaxTime(time.Duration(info.Options.MaxTime) * time.Second)
		}
	}
	raw, err := info.Collection.Indexes().DropAll(info.Ctx, info.DropOptions)
	if err != nil {
		return "", err
	}
	return raw.String(), nil
}

func DropOne(info *IndexInfo) (string, error) {
	if err := initIndexInfo(info); err != nil {
		return "", err
	}
	if info.DropOptions == nil {
		info.DropOptions = options.DropIndexes()
		if info.Options != nil && info.Options.MaxTime != 0 {
			info.DropOptions = info.DropOptions.SetMaxTime(time.Duration(info.Options.MaxTime) * time.Second)
		}
	}
	raw, err := info.Collection.Indexes().DropOne(info.Ctx, info.Name, info.DropOptions)
	if err != nil {
		return "", err
	}
	return raw.String(), nil
}

func IndexList(info *IndexInfo) error {
	if err := initIndexInfo(info); err != nil {
		return err
	}
	if info.Result == nil {
		return errors.New("mgo IndexList：result box cannot be nil")
	}
	if info.ListOptions == nil {
		info.ListOptions = options.ListIndexes()
		if info.Options != nil {
			if info.Options.BatchSize != 0 {
				info.ListOptions = info.ListOptions.SetBatchSize(info.Options.BatchSize)
			}
			if info.Options.MaxTime != 0 {
				info.ListOptions = info.ListOptions.SetMaxTime(time.Duration(info.Options.MaxTime) * time.Second)
			}
		}
	}
	cursor, err := info.Collection.Indexes().List(info.Ctx, info.ListOptions)
	if err != nil {
		return err
	}
	return cursor.All(info.Ctx, info.Result)
}

func ListSpecifications(info *IndexInfo) ([]*mongo.IndexSpecification, error) {
	if err := initIndexInfo(info); err != nil {
		return nil, err
	}
	if info.ListOptions == nil {
		info.ListOptions = options.ListIndexes()
		if info.Options != nil {
			if info.Options.BatchSize != 0 {
				info.ListOptions = info.ListOptions.SetBatchSize(info.Options.BatchSize)
			}
			if info.Options.MaxTime != 0 {
				info.ListOptions = info.ListOptions.SetMaxTime(time.Duration(info.Options.MaxTime) * time.Second)
			}
		}
	}
	return info.Collection.Indexes().ListSpecifications(info.Ctx, info.ListOptions)
}
